﻿using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Diagnostics;
using System.Linq;
using Lucene.Linq.Storage;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace Lucene.Linq.Azure
{
    public class AzureTableEntityStorage<T>
        : AzureEntityStorage, IEntityStorage<T>
        where T : TableServiceEntity, IEntity, new()
    {
        private readonly TableServiceContext _serviceContext;
        private readonly CloudTableClient _tableClient;
        private readonly string _tableName;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="account">cloud storage account</param>
        /// <param name="tableName">Name of table to store entities</param>
        /// <param name="retryPolicy">Retry policy</param>
        public AzureTableEntityStorage(CloudStorageAccount account, string tableName, RetryPolicy retryPolicy = null)
            : base(account)
        {
            _tableName = tableName;
            _tableClient = account.CreateCloudTableClient();
            _tableClient.RetryPolicy = retryPolicy ?? RetryPolicies.Retry(4, TimeSpan.Zero);
            _serviceContext = _tableClient.GetDataServiceContext();
            Trace.TraceInformation("Creating table");
            _tableClient.CreateTableIfNotExist(_tableName);
        }

        #region IEntityStorage<T> Members

        /// <summary>
        /// Puts an object in storage
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public IEntity GetEntity(string key)
        {
            return Get(key);
        }

        /// <summary>
        /// Puts an object in storage
        /// </summary>
        /// <param name="entity">entity to store</param>
        /// <returns>true if succesfull</returns>
        public bool Put(IEntity entity)
        {
            return Put(entity as T);
        }

        /// <summary>
        /// Checks if the entity exists on the storage medium
        /// </summary>
        /// <param name="key">The Key to use to verify if the entity exists</param>
        /// <returns>True if the entity exists</returns>
        /// <remarks>
        /// This call should not use the index to perform a lookup
        /// </remarks>
        public bool Exists(string key)
        {
            try
            {
                return _serviceContext
                    .CreateQuery<T>(_tableName).Where(e => e.PartitionKey == _tableName && e.RowKey == key).ToArray().
                    Any();
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Deletes the entity if it exists on the storage medium
        /// </summary>
        /// <param name="key">The key of the entity to delete</param>
        /// <returns>True if the Key does not, or no longer exists as a stored entity on the storage medium</returns>
        public bool Delete(string key)
        {
            try
            {
                if (Exists(key))
                {
                    T item = (_serviceContext.CreateQuery<T>(_tableName).Where(i => i.PartitionKey == _tableName
                                                                                    && i.RowKey == key)).Single();
                    if (item != null)
                    {
                        Trace.TraceInformation("item {0} {1}", item.PartitionKey, item.RowKey);
                        _serviceContext.DeleteObject(item);
                        _serviceContext.SaveChangesWithRetries();
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                Trace.TraceInformation(ex.ToString());
                return false;
            }
        }

        /// <summary>
        /// Returns all stored entities
        /// </summary>
        /// <returns></returns>
        public IEnumerable<string> All()
        {
            return _serviceContext
                .CreateQuery<T>(_tableName).Where(e => e.PartitionKey == _tableName).ToArray().Select(e => e.Key);
        }

        /// <summary>
        /// Gets an entity from storage
        /// </summary>
        /// <param name="key">Entity key</param>
        /// <returns></returns>
        public T Get(string key)
        {
            return (Query(key)).First();
        }

        /// <summary>
        /// Puts an entity in storage
        /// </summary>
        /// <param name="entity">Entity to store</param>
        /// <returns>true if succesfull</returns>
        public bool Put(T entity)
        {
            entity.PartitionKey = _tableName;
            entity.RowKey = entity.Key;

            if (Exists(entity.Key))
                Delete(entity.Key);

            _serviceContext.AddObject(_tableName, entity);
            _serviceContext.SaveChangesWithRetries(SaveChangesOptions.ReplaceOnUpdate);
            return true;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            // No need to dispose of anything
        }

        #endregion

        private IQueryable<T> Query(string key)
        {
            return
                _serviceContext.CreateQuery<T>(_tableName).Where(
                    e => e.PartitionKey == _tableName && (e.RowKey == key));
        }
    }
}